package com.open.source.javassist;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.AttributeInfo;
import javassist.bytecode.ClassFile;
import org.springframework.util.ResourceUtils;

/**
 * @author ZonLen since on 2022/3/24 上午12:06
 */
public final class ClassMaker {

  private final ClassPool classPool = ClassPool.getDefault();

  private final CtClass currentClass;

  private ClassFile classFile;

  private final String classpath;

  private ClassMaker(String className) throws FileNotFoundException {
    this.currentClass = classPool.makeClass(className);
    this.classpath =
        ResourceUtils.getURL(ResourceUtils.CLASSPATH_URL_PREFIX).getPath();
//            + File.separator
//            + className.replaceAll(
//            "\\.", File.separator);
  }

  public static ClassMaker create(String className) throws FileNotFoundException {
    return new ClassMaker(className);
  }

  private ClassFile getClassFile() {
    if (classFile == null) {
      this.classFile = currentClass.getClassFile();
    }
    return classFile;
  }

  public ClassMaker _annotations(Annotation[] annotations,
      BiFunction<Annotation, AnnotationMaker, AnnotationsAttribute> function) {
    if (annotations == null) {
      return this;
    }
    final List<AttributeInfo> attributes = getClassFile().getAttributes();
    for (Annotation annotation : annotations) {
      final AnnotationMaker maker = AnnotationMaker.instance(getClassFile().getConstPool());
      attributes.add(function.apply(annotation, maker));
    }
    return this;
  }

  public ClassMaker _annotation(Function<AnnotationMaker, AnnotationsAttribute> function) {
    final AnnotationMaker annotation = AnnotationMaker.instance(getClassFile().getConstPool());
    getClassFile().getAttributes().add(function.apply(annotation));
    return this;
  }

  public ClassMaker _extends(Class<?> supperClass)
      throws NotFoundException, CannotCompileException {
    if (null == supperClass) {
      return this;
    }
    currentClass.setSuperclass(classPool.get(supperClass.getName()));
    return this;
  }

  public ClassMaker _implements(Class<?>... interfaceClass) throws NotFoundException {
    if (null == interfaceClass) {
      return this;
    }
    for (Class<?> aClass : interfaceClass) {
      currentClass.addInterface(classPool.get(aClass.getName()));
    }
    return this;
  }

  public ClassMaker newField(Function<FieldMaker, CtField> function) throws CannotCompileException {
    final FieldMaker fieldMaker = FieldMaker.instance(classPool, currentClass);
    currentClass.addField(function.apply(fieldMaker));
    return this;
  }

  public ClassMaker newMethod(Function<MethodMaker, CtMethod> function)
      throws CannotCompileException {
    final MethodMaker methodMaker = MethodMaker.instance(classPool, currentClass);
    currentClass.addMethod(function.apply(methodMaker));
    return this;
  }


  public Class<?> buildClass() throws CannotCompileException, IOException {
    currentClass.writeFile(classpath);
    return currentClass.toClass();
  }

}
